// LemCoop
// Copyright (C) 2013  Oliver Naumann <bldrax@gmail.com>

// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.

// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.

// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.

#include "lobbystate.h"
#include "leveltransitionstate.h"

void LobbyState::ClientInit()
{
	// Startup as client.
	m_client = new RakNetClient;
	m_work_manager = m_client->work_manager();
	m_client->Connect("127.0.0.1", 4437);
	m_guid = m_client->GetGUID();

	// Create the chatfield for the client.
	ALLEGRO_FONT* chatfield_font = al_load_font(STANDARD_FONT.c_str(), STANDARD_FONT_SIZE, NULL);
	TextField* chatfield = new TextField("chatfield", 10, 728, 1004, 30,
		std::bind(&LobbyState::ClientEnterChatMessage, this, std::placeholders::_1), chatfield_font,
		STANDARD_TEXTFIELD_TEXT_COLOR, STANDARD_TEXTFIELD_PADDING, 1024);
	m_dialog->AddElement(chatfield);

	ClientWaitForConnection();
}

void LobbyState::ClientWaitForConnection()
{
	while (m_client->connection_status() == RakNetClient::PENDING)
	{
		// Wait until connection has been accepted or declined.
	}

	if (m_client->connection_status() == RakNetClient::NO_CONNECT)
	{
		// TODO: Connection failed. Go back to the previous state or something like that.
	}
}

void LobbyState::ClientCleanup()
{
	if (m_shutdown_network_on_state_end)
	{
		m_client->Shutdown();
		delete m_client;
	}
}

void LobbyState::DoClientWork()
{
	// Get Jobs done. If the game has been started, we don't want to read any jobs anymore because they might
	// already be for the next state so we don't want to trash them.
	Job next_job;
	while (m_work_manager->GetJobToLocal(next_job) && !m_game_started)
	{
		unsigned char type_id;
		next_job.data->Read(type_id);
		switch (type_id)
		{
		case ID_DISCONNECTION_NOTIFICATION:
		case ID_CONNECTION_LOST:
			ClientProcessClosedConnection();
			break;

		case ID_REMOVE_CLIENT:
			ClientProcessRemoveClient(next_job);
			break;

		case ID_CLIENT_INFORMATION_REQUEST:
			SendMyClientInformation();
			break;

		case ID_CLIENT_INFORMATION:
			ClientProcessClientInformation(next_job);
			break;

		case ID_CHAT_MESSAGE:
			ClientProcessChatMessage(next_job);
			break;

		case ID_LEVELINFO:
			ClientProcessLevelInfo(next_job);
			break;

		case ID_START_GAME:
			ClientProcessStartGame();
			break;
		};

		next_job.JobDone();
	}
}

void LobbyState::ClientProcessClosedConnection()
{
	// We have been disconnected.
	// TODO: Do something that makes sense (i.e. state change, ...)
	m_shutdown_network_on_state_end = true;
	m_engine->set_end_game(true);
}

void LobbyState::ClientProcessRemoveClient(Job remove_client_job)
{
	RakNet::RakString guid_string;
	RakNet::StringCompressor::Instance()->DecodeString(&guid_string, 64, remove_client_job.data);
	m_client_list.RemoveClient(std::string(guid_string.C_String()));

	m_rebuild_color_helpsymbols = true;
}

void LobbyState::ClientProcessClientInformation(Job connection_information_job)
{
	RakNet::RakString guid_string;
	RakNet::StringCompressor::Instance()->DecodeString(&guid_string, 64, connection_information_job.data);
	std::string guid(guid_string.C_String());

	// Fill in client information if the client has alreaby been created.
	ClientConnection* client_to_update;
	if (m_client_list.GetClient(guid, client_to_update))
	{
		// Set all attributes.
		RakNet::RakString name_string;
		RakNet::StringCompressor::Instance()->DecodeString(&name_string, 64, connection_information_job.data);
		client_to_update->set_name(std::string(name_string.C_String()));
		for (int p = 0; p < MAX_PLAYERS; p++)
		{
			bool control;
			connection_information_job.data->Read(control);
			client_to_update->set_control_at(p, control);
		}

		// The player may now control different colors, so update the help symbols.
		m_rebuild_color_helpsymbols = true;
	}
	// If the client guid is unknown, then it has to be a new client.
	else
	{
		ClientConnection new_client;

		new_client.set_guid(guid);
		RakNet::RakString name_string;
		RakNet::StringCompressor::Instance()->DecodeString(&name_string, 64, connection_information_job.data);
		for (int p = 0; p < MAX_PLAYERS; p++)
		{
			bool control;
			connection_information_job.data->Read(control);
			new_client.set_control_at(p, control);
		}

		m_client_list.AddClient(new_client);
	}
}

void LobbyState::ClientProcessChatMessage(Job chat_message_job)
{
	RakNet::RakString message_string;
	RakNet::StringCompressor::Instance()->DecodeString(&message_string, 64, chat_message_job.data);

	unsigned char color[3];
	for (unsigned c = 0; c < 3; c++)
		chat_message_job.data->Read(color[c]);

	// Show in the chatbox.
	GuiElement* element;
	if (m_dialog->GetElement("chatbox", element))
	{
		((TextBox*)element)->AddLine(std::string(message_string.C_String()), color);
	}
}

void LobbyState::ClientEnterChatMessage(std::string message)
{
	GuiElement* element;
	if (m_dialog->GetElement("chatfield", element))
	{
		((TextField*)element)->DeleteText();
	}

	message = "[" + m_client_name + "]: " + message;
	ClientSendChatMessage(message, STANDARD_TEXTBOX_TEXT_COLOR);
}

void LobbyState::ClientSendChatMessage(std::string message, const unsigned char color[3])
{
	Job job(ID_CHAT_MESSAGE);
	RakNet::StringCompressor::Instance()->EncodeString(RakNet::RakString(message.c_str()), 64, job.data);
	job.data->Write(color[0]);
	job.data->Write(color[1]);
	job.data->Write(color[2]);

	m_work_manager->JobToRemote(job);
}

void LobbyState::ClientProcessLevelInfo(Job levelinfo_job)
{
	levelinfo_job.data->Read(m_level_chosen);
	if (m_level_chosen)
	{
		levelinfo_job.data->Read(m_current_level.id);
		levelinfo_job.data->Read(m_current_level.number);
		levelinfo_job.data->Read(m_current_level.player_number);

		RakNet::RakString name_string;
		RakNet::StringCompressor::Instance()->DecodeString(&name_string, 64, levelinfo_job.data);
		m_current_level.name = std::string(name_string.C_String());

		// For the client, m_number_of_players_set is the last known number of players.
		m_number_of_players_set = m_current_level.player_number;
	}

	// We don't want to draw the color helpsymbols when no level is chosen. This means, an update might be necessary.
	m_rebuild_color_helpsymbols = true;
}

void LobbyState::ClientProcessStartGame()
{
	m_game_started = true;
	m_shutdown_network_on_state_end = false;

	LevelTransitionState* next_state = new LevelTransitionState(m_engine);
	next_state->Init(m_is_server, m_server, m_client, m_client_list, "");
	m_engine->SetNextState(next_state);
}

void LobbyState::ClientUpdate()
{
	DoClientWork();
}